﻿
using Boilen.Primitives.Members;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;


namespace Boilen.Primitives.Implementers {

    /// <inheritdoc/>
    public abstract class Implementer<T> : IImplementer {

        private readonly PartialType parent_;
        private readonly string name_;
        private readonly string typeName_;
        private readonly string description_;
        private readonly Doc.ReplacementsDictionary replacements_ = new Doc.ReplacementsDictionary( );

        private Member[] members_;


        /// <inheritdoc/>
        public PartialType Parent { get { return this.parent_; } }

        /// <inheritdoc/>
        public string Name { get { return this.name_; } }

        /// <inheritdoc/>
        public string MemberName { get { return Util.Capitalize( this.Name ); } }

        /// <inheritdoc/>
        public Type Type { get { return typeof( T ); } }

        /// <inheritdoc/>
        public string TypeName { get { return this.typeName_; } }

        /// <inheritdoc/>
        public string Description { get { return this.description_; } }

        /// <inheritdoc/>
        public IEnumerable<Member> Members {
            get {
                if( this.members_ == null )
                    this.members_ = this.GetMembers( )
                        .Where( m => this.Condition.Compatible( m.Condition ) )
                        .Select( m => m.Condition.IsUnconditional ? m.SetCondition( this.Condition ) : m )
                        .ToArray( );
                return this.members_.AsEnumerable( );
            }
        }

        /// <inheritdoc/>
        public Doc.ReplacementsDictionary Replacements { get { return this.replacements_; } }

        /// <inheritdoc/>
        public CompilationSymbol Condition { get; set; }

        /// <summary>Gets a value indicating whether the description parameter should be verified.</summary>
        protected virtual bool EnsureDescription { get { return !this.Parent.IsInternal; } }


        /// <summary>
        /// Initializes a new <see cref="Implementer{T}"/> instance.
        /// </summary>
        /// <param name="parent">The parent of the implementer.</param>
        /// <param name="name">The name of the implementer.</param>
        /// <param name="description">The documentation summary for the implementer.</param>
        protected Implementer( PartialType parent, string name, string description ) {
            Ensure.NotNull( parent );
            Ensure.NotNullOrEmpty( name );

            this.parent_ = parent;
            this.name_ = Util.Lowercase( name );
            this.description_ = description == null ? null : Util.Lowercase( description ).TrimEnd( '.' );
            this.typeName_ = this.Parent.TypeRepository.GetTypeName( typeof( T ) );
        }


        /// <inheritdoc/>
        public virtual void Prepare( ) {
            if( this.EnsureDescription ) {
                if( GlobalSettings.ExternalDocumentationPrefix != null && !string.IsNullOrEmpty( this.description_ ) )
                    throw new InvalidOperationException( "Description on " + this.MemberName + " member on public type cannot be used when specifying external documentation." );
                if( GlobalSettings.ExternalDocumentationPrefix == null && string.IsNullOrEmpty( this.description_ ) )
                    throw new InvalidOperationException( "Description on " + this.MemberName + " member must be specified when not using external documentation." );
            }
        }

        /// <summary>
        /// Assigns the value used for the <see cref="Condition"/> property.
        /// </summary>
        [EditorBrowsable( EditorBrowsableState.Advanced )]
        public Implementer<T> SetCondition( CompilationSymbol condition ) {
            this.Condition = condition;
            return this;
        }

        /// <summary>
        /// Assigns the value used for the <see cref="Condition"/> property.
        /// </summary>
        public Implementer<T> SetCondition( string condition ) {
            this.Condition = new CompilationSymbol( condition );
            return this;
        }


        /// <summary>
        /// Creates the implementation members for the implementer.
        /// </summary>
        protected abstract IEnumerable<Member> GetMembers( );

        /// <summary>
        /// Creates a new <see cref="Doc"/> instance with the appropriate values.
        /// </summary>
        protected Doc CreateDoc( string fullyQualifiedName = null ) {
            return new Doc( fullyQualifiedName ?? this.MemberName, this.Name, this.TypeName, this.Parent.TypeName )
                .AddReplacements( this.Replacements );
        }

        /// <summary>
        /// Creates a <see cref="ParameterMember"/> from a parameter on an existing method.
        /// </summary>
        /// <remarks>
        /// Used with <see cref="MethodMember.GetParameters"/>.
        /// </remarks>
        protected ParameterMember CreateParameter( string name, Type type, string description ) {
            string typeName = type.IsGenericParameter
                ? this.Parent.TypeName
                : this.Parent.TypeRepository.GetTypeName( type );
            return CreateParameter( name, typeName, description );
        }

        /// <summary>
        /// Creates a <see cref="ParameterMember"/> from a parameter on an existing method.
        /// </summary>
        /// <remarks>
        /// Used with <see cref="MethodMember.GetParameters"/>.
        /// </remarks>
        protected ParameterMember CreateParameter( string name, string typeName, string description ) {
            return new ParameterMember( name, typeName ) {
                Doc = this.CreateDoc( ).AddParam( name, description )
            };
        }


        /// <summary>
        /// Ensures the specified implementer is included in the <see cref="Parent"/>'s collection of implementers.
        /// </summary>
        /// <typeparam name="TImplementer">The type of implementer required.</typeparam>
        /// <param name="target">The target compilation symbol.</param>
        /// <param name="creator">A delegate for creating an implementer, if it does not exist.</param>
        /// <returns>The properly configured implementer.</returns>
        protected TImplementer RequireImplementer<TImplementer>( CompilationSymbol target, Action<Action<TImplementer>> creator )
            where TImplementer : class, IImplementer {
            Ensure.NotNull( creator );

            TImplementer[] implementers = this.Parent.Implementers.OfType<TImplementer>( ).ToArray( );

            TImplementer matchingImplementer = implementers.FirstOrDefault( i => i.Condition.Covers( target ) );
            if( matchingImplementer != null ) {
                return matchingImplementer;
            }
            else {
                TImplementer implementer = implementers.FirstOrDefault( );

                if( implementer != null )
                    implementer.Condition = CompilationSymbol.Combine( implementer.Condition, target );
                else
                    creator( i => { implementer = i; implementer.Condition = target; } );

                return implementer;
            }
        }

        /// <summary>
        /// Prepares all <see cref="IImplementer"/> targets for the construct.
        /// </summary>
        /// <typeparam name="TTarget">The type of the implementer target.</typeparam>
        protected void PrepareTargets<TTarget>( Action<TTarget> prepare ) {
            Ensure.NotNull( prepare );

            var targets = this.Parent.Implementers.OfType<TTarget>( );
            foreach( TTarget target in targets )
                prepare( target );
        }

        /// <summary>
        /// Retrieves construct-specific data from all <see cref="IImplementer"/> targets.
        /// </summary>
        /// <typeparam name="TTarget">The type of the implementer target.</typeparam>
        /// <typeparam name="TData">The type of data to retreive from the implementer target.</typeparam>
        protected IEnumerable<TData> GetTargetData<TTarget, TData>( Func<TTarget, bool> filter, Func<TTarget, TData> getData ) {
            Ensure.NotNull( filter, getData );

            return this.Parent.Implementers
                .OfType<TTarget>( )
                .Where( filter )
                .Select( getData );
        }

    }

}
